﻿using HARTCalibrationTool.Commands;
using HARTCalibrationTool.Commands.HART;
using HARTCalibrationTool.SerialPortManager;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace HARTCalibrationTool.CommandsQueue
{
    public class CMDsQueue
    {
        private bool _isRunning = false;
        private readonly Queue<(CommandsBase command, TimeSpan delay)> _commandsQueue = new Queue<(CommandsBase command, TimeSpan delay)>();
        private CancellationTokenSource _cts;
        private bool _bStopAfExeSuccess;
        private bool _bStopAfTimeout;
        private bool _bStopAfRetryFail;

        /*  
         *  bStopAfExeSuccess : 命令发送完成并且收到返回数据后是否停止执行命令队列中的命令，
         *                      比如在HART轮询地址时，需要在轮询成功后停止轮询，需要bStopAfExeSuccess=true。
         *  bStopAfTimeout : 这个标志位控制发送命令后超时没有收到返回信息的情况下，是否继续执行命令队列中的命令。
         *                  比如在HART轮询地址时，没有轮询成功之前需要继续执行轮询，所以需要bStopAfTimeout=false。
         *  _bStopAfRetryFinish : 命令retry 3次后仍然失败，这个标志位控制3次失败后是否停止执行剩下的命令。
         *                      比如一般命令retry 3次后仍然失败，会停止；但轮询地址时我需要它继续执行。
         */
        public CMDsQueue(bool bStopAfExeSuccess = false, bool bStopAfTimeout = true, bool bStopAfRetryFail = true) 
        { 
            _bStopAfExeSuccess = bStopAfExeSuccess;
            _bStopAfTimeout = bStopAfTimeout;
            _bStopAfRetryFail = bStopAfRetryFail;
        }

        public void Start()
        {
            if (!_isRunning)
            {
                ExecuteNextCommand();
            }
        }

        private async void ExecuteNextCommand()
        {
            SerialPortsBase serialPort = null;

            if (_commandsQueue.Count == 0)
            {
                _isRunning = false;
                return;
            }

            _isRunning = true;

            var (command, delay) = _commandsQueue.Peek();
            serialPort = command.SerialPortMager;
            serialPort.SetCurrentCMD(command);
            serialPort.CommandCompleted += OnCommandCompleted;

            _cts = new CancellationTokenSource();
            var commandTask = command.ExecuteCMD();
            var timeoutTask = Task.Delay(TimeSpan.FromSeconds(1), _cts.Token);

            var completedTask = await Task.WhenAny(commandTask, timeoutTask);

            if (completedTask == timeoutTask)
            {
                // Send Command timeout
                SerialPortsBase.PrintMessage("Command:" + serialPort.CurrentCMD.CommandName + ", 发送超时！");
                _commandsQueue.Clear();
                serialPort.CommandCompleted -= OnCommandCompleted;
                command.ClearCMDsExeCnt();
                command.SetCMDsExecutingStatus(false);

                return;
            }
            else
            {
                await WaitForResponseAndValidateAysnc(command);
            }

            if (command.GetCMDExeCnt() >= 3)
            {
                if (_bStopAfRetryFail == true)
                {
                    _commandsQueue.Clear();
                    command.SetCMDsExecutingStatus(false);
                }
                else
                {
                    _commandsQueue.Dequeue();
                }

                command.ClearCMDsExeCnt();
            }

            if (_commandsQueue.Count != 0)
            {
                await Task.Delay(delay);
                ExecuteNextCommand();
            }
            else
            {
                command.SetCMDsExecutingStatus(false);
            }
        }

        private async Task WaitForResponseAndValidateAysnc(CommandsBase command)
        { 
            var tcs = new TaskCompletionSource<bool>();
            EventHandler handler = (s, e) => tcs.TrySetResult(true);
            SerialPortsBase serialPort = command.SerialPortMager;

            try
            {
                serialPort.CommandCompleted += handler;

                var timeoutTask = Task.Delay(TimeSpan.FromSeconds(2));
                var responseTask = tcs.Task;

                var completedTask = await Task.WhenAny(responseTask, timeoutTask);

                if (completedTask == timeoutTask)
                {
                    SerialPortsBase.PrintMessage("Command:" + serialPort.CurrentCMD.CommandName + ", 响应超时！");

                    if (_bStopAfTimeout == true)
                    {
                        _commandsQueue.Clear();
                        command.SetCMDsExecutingStatus(false);
                    }
                    else
                    {
                        command.CMDsExeCntIncrement();
                    }
                }
                else
                {
                    _commandsQueue.Dequeue();
                    command.ClearCMDsExeCnt();

                    // 轮询地址0~15，_bStopAfExeSuccess 用于控制收到回复后是否会执行队列中剩下的命令
                    if ((_bStopAfExeSuccess == true) && (command is HARTCMDsBase))
                    {
                        _commandsQueue.Clear();
                        command.SetCMDsExecutingStatus(false);
                    }
                }
            }
            catch (Exception ex)
            {
                SerialPortsBase.PrintMessage(ex.Message);
            }
            finally
            {
                serialPort.CommandCompleted -= handler;
            }
        }

        public void EnqueueCommand(CommandsBase command, TimeSpan delay)
        {
            _commandsQueue.Enqueue((command, delay));
        }

        private void OnCommandCompleted(object sender, EventArgs e)
        {
            // Notify that the command has completed and proceed to the next command
            _cts?.Cancel();
        }
    }
}
